#ifndef FILESERVER_H
#define FILESERVER_H


#include <iostream>
#include "Server.h"
#include "Disk.h"
#include "FreeList.h"
#include "OurDirectory.h"
#include "File.h"
#include <pthread.h>
#include <vector>

using namespace std;

#include "muscle/message/Message.h"

using namespace muscle;


static pthread_mutex_t GOFTLock;

	static void LockGOFT()
	{
		pthread_mutex_lock(&GOFTLock);
	}
	
	static void UnlockGOFT()
	{
		pthread_mutex_unlock(&GOFTLock);
	}

struct GlobalOpenFile
{
	char* FileName;
	int NumUsers;
	int WritePointer;	
	pthread_mutex_t FileLock;
};

static vector<GlobalOpenFile> GOFT;

struct FileSystem{
	Disk* disk;
	FreeList *free;
	Directory *dir;
};

struct ClientInfo
{
	int id;
	Server* server;
	FileSystem* filesys;
};


struct OpenFile{
	String fileName;
	File* file;
	int mode;
	int fd;
};



static int SearchGlobalOFT(char* fname)
{
		for (int i = 0; i<GOFT.size(); i++)
		{
				if(strcmp(GOFT[i].FileName, fname) == 0)
				{
						return i;
				}
		}
		return -1;
}

static int SearchOFT(vector<OpenFile> OFT, int fd){
		for(int i =0; i< OFT.size(); i++){
			if(OFT[i].fd == fd){
				return i;
			}
		}
		return -1;
	}

static int SearchOFT(vector<OpenFile> OFT, String fileName){
		for(int i =0; i< OFT.size(); i++){
			if(strcmp(OFT[i].fileName.Cstr(),fileName.Cstr()) == 0){
				return i;
			}
		}
		return -1;
	}


static void* CinServerThread(void* filesys){

  while(true){
    cout<< "Enter command" << endl;
    string command;
    cin>>command;
    ClientInfo* ci = (ClientInfo*)filesys;
    if(strcmp(command.c_str(), "freelist")== 0){
      ci->filesys->free->lock();
      ci->filesys->free->Output();
      ci->filesys->free->unlock();
    }
    else if(strcmp(command.c_str(), "directory")==0){
      vector<DirectoryEntry>* files = ci->filesys->dir->list();
      for(int i = 0; i < files->size(); i++){
	char name[DirectoryEntry::MAX_NAME_LENGTH];
	files->at(i).getFileName(name);
	cout<< name << endl;
      }
    }
    else if(strcmp(command.c_str(), "quit")==0){
      cout<< "Server should quit now" << endl;
      cout<< ci->server->Close();
      break;
    }
    else{
      cout<< command << " command does not exist" << endl;
    }
  }
}

static void* HandleClient(void* clientInfo){

	string EXIT = "Exit";
	string OPEN = "Open";
	string CLOSE = "Close";
	string WRITE = "Write";
	string READ = "Read";
	string EXISTS = "Exists";
	string DELETE = "Delete";
	string LIST = "List";
	String FileName("FileName");
	String Mode("Mode");
	String Create("Create");
	String FD("FD");
	String Buffer("Buffer");
	String Length("Length");

	int fdcount = 0;

	vector<OpenFile> OFT;

ClientInfo* ci = (ClientInfo*)clientInfo;
			int cid = ci->id;
			Server* server = ci->server;
			FileSystem* filesys = ci->filesys;
			
		 while(true){

			
			uint32 messageSize;

			int size = server->Receive(cid, &messageSize, sizeof(int));

			if(size == -1){
			cout<<"client disconnected" << endl;
			break;
			}

			uint8 buffer[messageSize];

			if(server->Receive(cid, buffer, messageSize) < 1){
				cout<< "client disconnected" << endl;
				break;
			}

			Message m;
			cout << "Size is " << messageSize<< endl;
			if(B_ERROR == m.Unflatten(buffer, messageSize)){
				cout<< "Error unflattening" << endl;
			}

			String OperationString;

			String FileOperation("FileOperation");

			int Operation;	

			if(m.FindString(FileOperation, OperationString)== B_NO_ERROR){
				cout<< OperationString.Cstr() << endl;
				if(strcmp(OperationString.Cstr(), EXIT.c_str()) == 0){
					Operation = 0;
				}
				else if(strcmp(OperationString.Cstr(), OPEN.c_str()) == 0){
					Operation = 1;
					
				}
				else if(strcmp(OperationString.Cstr(), CLOSE.c_str()) == 0){
					Operation = 2;
				}
				else if(strcmp(OperationString.Cstr(), WRITE.c_str()) == 0){
					Operation = 3;
				}
				else if(strcmp(OperationString.Cstr(), READ.c_str()) == 0){
					Operation = 4;
				}
				else if(strcmp(OperationString.Cstr(), EXISTS.c_str()) == 0){
					Operation = 5;
				}
				else if(strcmp(OperationString.Cstr(), DELETE.c_str()) == 0){
					Operation = 6;
				}
				else if(strcmp(OperationString.Cstr(), LIST.c_str()) == 0){
					Operation = 7;
				}
				else{
					Operation = -1;
				}
				
			}
			else{
				cout<< "MessageError:Could not find string" << endl;
			}

			if(Operation == 0){
				server->Send(cid, &Operation, sizeof(int));
				cout<<"Connection " << cid << " Closed" << endl;
				break;
			}
			else if(Operation == 1){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File opened command"<< endl;
				
				//Open requested file

				String fileName;
				int32 ModeNum;
				bool CreateValue;
				File* curFile;
							

				if(m.FindString(FileName, fileName)== B_NO_ERROR){
					if(m.FindInt32(Mode, ModeNum)== B_NO_ERROR){
						if(m.FindBool(Create, &CreateValue)== B_NO_ERROR){//unpacking the muscle message
							int returnValue = 0;

							if(SearchOFT(OFT, fileName)  < 0){

								try{
									char* filename = (char*)fileName.Cstr();
									curFile = new File(filesys->dir, filename, ModeNum, CreateValue);
								}
								catch(FileNotFoundException e){
									cout<< "File wasn't found"<< endl;
									returnValue = -1;
								}

							

								
								if(ModeNum > 0 && ModeNum < 3){
									//Add File to OFT
									OpenFile of;
									of.file = curFile;
									of.mode = ModeNum;
									of.fd = fdcount;
									returnValue = fdcount;
									fdcount++;
									of.fileName = fileName;
									OFT.push_back(of);
									
									
									//check GOFT, if there, increment NumUsers
									//if not, add it
									LockGOFT();
									int GOFTIndex = SearchGlobalOFT((char*)fileName.Cstr());
									if(GOFTIndex < 0)
									{
										//initialize a new GlobalOpenFile
										//and add it to GOFT
										
										GlobalOpenFile newGOF;
										newGOF.FileName = (char*)fileName.Cstr();
										newGOF.NumUsers = 1;
										newGOF.WritePointer = 0;
										pthread_mutex_init(&newGOF.FileLock, NULL);
										GOFT.push_back(newGOF);
									}
									else
									{
										GOFT[GOFTIndex].NumUsers++;	
									}
									UnlockGOFT();
									
								}
								else{
									cout<< "File Created as closed or Bad mode" << endl;
								}

							}
							else{
								returnValue = -1;
							}
							cout<< returnValue << endl;
							server->Send(cid, &returnValue, sizeof(int));//returning a file descriptor
							
						}
						else{//Run this on find string error
							cout<< "Error Finding Create" << endl;
						}
					}
					else{//Run this on find string error
						cout<< "Error Finding the Mode " << endl;
					}
				}
				else{//Run this on find string error
					cout<< "Error Finding FileName" << endl;
				}
				
			}//End of Opening File
			else if(Operation == 2){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File close command"<< endl;

				//Closing file
				int32 fd;
				

				if(m.FindInt32(FD, fd)== B_NO_ERROR){//unpacking the muscle message
							int returnValue;
							char* fname;
							int index = SearchOFT(OFT, fd);
							if(index > -1){
								//copy the name so we can find it in the GOFT
								fname = (char*)OFT[index].fileName.Cstr(); 
								OFT[index].file->Close();
								OFT.erase(OFT.begin() + index);
								returnValue = 0;
							}
							else{
								returnValue = -1;
							}

							LockGOFT();//lock the GOFT
							int GOFTIndex = SearchGlobalOFT(fname);
							if(GOFTIndex > 0)
							{
								if(GOFT[GOFTIndex].NumUsers > 1) //there's others using file
									GOFT[GOFTIndex].NumUsers--;
								else
									GOFT.erase(GOFT.begin() + GOFTIndex);	
							}
							UnlockGOFT();//unlock the GOFT

							server->Send(cid, &returnValue, sizeof(int));

							for(int i = 0; i < OFT.size(); i++){
							  cout<< OFT[i].fileName.Cstr() << endl;
							}
							
					}
					else{//Run this on find string error
						cout<< "Error Finding FD" << endl;
					}
			}//end of closing file
			else if(Operation == 3){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File write command"<< endl;


				int32 fd;
				int length;

				
				if(m.FindInt32(FD, fd)== B_NO_ERROR){
				                cout<< "Made it here" << endl;
					if(m.FindInt32(Length, (int32*)&length)== B_NO_ERROR){//unpacking the muscle message
					                cout<< "Made it here" << endl;
							int returnValue;
							char buffer[length];
							server->Receive(cid, buffer, length);
							cout<< "value of length is: " << length << endl;

							int index = SearchOFT(OFT, fd);
							cout<< index << endl;
							
							
							//need to get the most recent file ptr from GOFT
							char* fname = (char*)OFT[index].fileName.Cstr();
							LockGOFT();//Lock the GOFT
							int GOFTIndex = SearchGlobalOFT(fname);
							
							if(index < 0) cout << "File was found in local FileTable, but absent from GOFT\n";
							
							//lock the actual FILE for writing
							pthread_mutex_lock(&GOFT[GOFTIndex].FileLock);
							
							//update this client's file pointer to reflect any changes that were made by other clients
							OFT[index].file->SetFilePointer(GOFT[GOFTIndex].WritePointer);
							
							//now perform the write operation
							returnValue = OFT[index].file->Write(buffer, length);
							
							//get the newly modified file pointer and give it to GOFT
							GOFT[GOFTIndex].WritePointer = OFT[index].file->GetFilePointer();
							
							//now we can unlock the file, and then the GOFT
							pthread_mutex_unlock(&GOFT[GOFTIndex].FileLock);
							UnlockGOFT();
							
							server->Send(cid, &returnValue, sizeof(int));
		
					}
					else{//Run this on find string error
						cout<< "Error Finding Buffer" << endl;
					}
				}
				else{//Run this on find string error
					cout<< "Error Finding FD" << endl;
				}

			}//end of write
			else if(Operation == 4){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File read command"<< endl;

				int32 fd;
				int32 length;


				if(m.FindInt32(FD, fd)== B_NO_ERROR){
					if(m.FindInt32(Length, &length)== B_NO_ERROR){//unpacking the muscle message
							int returnValue;
							char buffer[length];

							int index = SearchOFT(OFT, fd);
							
							
							//Lock the GOFT
							LockGOFT();
							
							//find this file in the GOFT
							int GOFTIndex = SearchGlobalOFT((char*)OFT[index].fileName.Cstr());
							
							//Lock it for reading
							pthread_mutex_lock(&GOFT[GOFTIndex].FileLock);
							
							//perform the read operation
							returnValue = OFT[index].file->Read(buffer, (int)length);
							
							//unlock the file, then the GOFT
							pthread_mutex_unlock(&GOFT[GOFTIndex].FileLock);
							UnlockGOFT();
							
							server->Send(cid, &returnValue, sizeof(int));



							if(returnValue < 0){
								cout<< "File Read Error" << endl;
							}
							else if(returnValue == 0){
								cout<< "EOF Reached" << endl;
							}
							else{//pack up the bytes read and send over network
									server->Send(cid, buffer, returnValue);
							}
		
					}
					else{//Run this on find string error
						cout<< "Error Finding Buffer" << endl;
					}
				}
				else{//Run this on find string error
					cout<< "Error Finding FD" << endl;
				}


			}//end of read
			else if(Operation == 5){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File exists command"<< endl;
	
				String fileName;
							

				if(m.FindString(FileName, fileName)== B_NO_ERROR){//unpacking the muscle message
							int returnValue;

							if(filesys->dir->Search((char*)fileName.Cstr())> -1){
								returnValue = 1;
							}
							else{
								returnValue = 0;
							}


							server->Send(cid, &returnValue, sizeof(int));//returning an error condition
							
				}
				else{//Run this on find string error
					cout<< "Error Finding FileName" << endl;
				}

				
			}//end of file exists
			else if(Operation == 6){
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File delete command"<< endl;

				String fileName;
							

				if(m.FindString(FileName, fileName)== B_NO_ERROR){//unpacking the muscle message
						int index = SearchOFT(OFT, fileName);
						int returnValue = 0;
						
						LockGOFT();
						int GOFTIndex = SearchGlobalOFT((char*)fileName.Cstr());

						//ONLY DELETE IF THERE IS NOBODY ELSE USING THE FILE
						//this means that WE MUST CLOSE OUR OWN COPY OF THE FILE
						//BEFORE WE CAN DELETE IT
						
						if(GOFTIndex < 0){ 
						//it's not open by anyone, so we can delete it
						
						//we don't need to lock the file because nobody 
						//can be accessing it at this point
							if(index > -1){
								//remove from OFT
								delete OFT[index].file;
								OFT.erase(OFT.begin() + index);
							}
							
							//Create the file to delete it
							try{
								File file(filesys->dir, (char*)fileName.Cstr(), 0, false);
								cout<<"made it to FILE::Remove()" << endl;
								file.Remove();
								cout<< "done with FILE::Remove()" << endl;
							}
							catch(FileNotFoundException e){
								cout<< "File was not found" << endl;
								returnValue = -1;
							}								
					}
					else //there are other users using the file
					{
						returnValue = -1;
					}
					
					//unlock the GOFT
					UnlockGOFT();
					
					server->Send(cid, &returnValue, sizeof(int));//returning an error condition			
				}
				else{//Run this on find string error
					cout<< "Error Finding FileName" << endl;
				}

				
			}//end of delete file
			else if(Operation == 7){
			//    LIST COMMAND	
				//server->Send(cid, &Operation, sizeof(int));
				cout<< "File list command"<< endl;
				
				/*
				 * Locking the GOFT now prevents entries 
				 * being added or removed during a list operation
				 */
				LockGOFT();
				
				//get directory entries
				vector<DirectoryEntry>* entries = filesys->dir->list();
				int size = entries->size();
				server->Send(cid, &size, sizeof(int));//sending number of entries

				for(int i = 0; i < entries->size(); i++){
					//for each entry send a message containing its filename
					//create muscle String from directory entry
					char Name[DirectoryEntry::MAX_NAME_LENGTH];
					entries->at(i).getFileName(Name);
					String fileName = Name;
					File cur(filesys->dir,Name, 0, false);
					String size ("Size");
					int32 fileSize = cur.GetSize();
										
 
					Message n;
					if(n.AddString(FileName, fileName)== B_NO_ERROR)						
					{
						if (n.AddInt32(size, fileSize) == B_NO_ERROR)
						{						
							int flatsize = n.FlattenedSize();
							server->Send(cid, &flatsize, sizeof(int));
							uint8 returnbuf[flatsize];
							n.Flatten(returnbuf);
							server->Send(cid, returnbuf, flatsize);
						}
						else{
						cout<< "Error Adding Size"<<endl;
						}
					}
					else{
						cout<< "Error Adding Buffer" << endl;
					}
				}
				
				//unlock the GOFT
				UnlockGOFT();
				
				
			}
			else{
				cout<< "Operation invalid" << endl;
			}

			/**This is some archaic code
			int flatsize = m.FlattenedSize();
			server->Send(cid, &flatsize, sizeof(int));
			uint8 echobuf[flatsize];
			m.Flatten(echobuf);
			server->Send(cid, echobuf, flatsize);
			**/
		}
	}


class FileServer{

	Server* m_server;

	pthread_t* threads[100];
	


	FileSystem filesys;
	


public:

	FileServer(int port, bool format){
		
		pthread_mutex_init(&GOFTLock, NULL);
		//build file system
		if(format){
			//File System is formatted
			filesys.disk = new Disk("Disk1", 100, Disk::DEFAULT_BLOCK_SIZE);
    			filesys.free =  new FreeList("Disk1", true);
    			filesys.dir = new Directory(filesys.free, true);
			cout<<"New File System Built" << endl;
		}
		else{
			filesys.disk = new Disk("Disk1", 100, Disk::DEFAULT_BLOCK_SIZE);
			filesys.free =  new FreeList("Disk1", false);
			filesys.dir = new Directory(filesys.free, false);
			cout<<"File System Rebuilt" << endl;
		}
		
		
		GOFT = vector<GlobalOpenFile>(); //initialize the Global Open File table


		cout<< "Listening on port " << port << endl;
		m_server = new Server(port, "tcp", 4);
	}

	~FileServer(){
		delete m_server;
	}

	void Start(){
	
		//server starts listening
		m_server->Listen();

		//spawn server input thread
		ClientInfo param;
		param.id = -1;
		param.server = m_server;
		param.filesys = &filesys;

		if(0 != pthread_create(new pthread_t, NULL, CinServerThread, &param)){
				cout<< "Thread Creation Failed" << endl;
		}
		else{
		  cout<< "Cin thread created" << endl;
		}

		while(true){		

			//server accepts connection
  			int id = m_server->Accept();

			if(id < 0){
			  break;
			}

			threads[id] = new pthread_t();

			ClientInfo ci;
			ci.id = id;
			ci.server = m_server;
			ci.filesys = &filesys;
			if(0 != pthread_create(threads[id], NULL, HandleClient, &ci)){
				cout<< "Thread Creation Failed" << endl;
			}
			else{
				cout<< "New Client Proxy Created. Client id is " << ci.id << endl;
			}

		}

	}



};


#endif

